package cmd

import (
	"bytes"
	"fmt"
	"github.com/spf13/cobra"
	"net"
	"strconv"
	"strings"
	"time"
)

var netbios bool
var smb bool
var oxid bool


var oxidQuery1 = [...]byte{
	0x05, 0x00, 0x0b, 0x03, 0x10, 0x00, 0x00, 0x00, 0x48, 0x00,
	0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xb8, 0x10, 0xb8, 0x10,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x01, 0x00, 0xc4, 0xfe, 0xfc, 0x99, 0x60, 0x52, 0x1b, 0x10,
	0xbb, 0xcb, 0x00, 0xaa, 0x00, 0x21, 0x34, 0x7a, 0x00, 0x00,
	0x00, 0x00, 0x04, 0x5d, 0x88, 0x8a, 0xeb, 0x1c, 0xc9, 0x11,
	0x9f, 0xe8, 0x08, 0x00, 0x2b, 0x10, 0x48, 0x60, 0x02, 0x00,
	0x00, 0x00,
}

var oxidQuery2 = [...]byte{
	0x05, 0x00, 0x00, 0x03, 0x10, 0x00, 0x00, 0x00, 0x18, 0x00,
	0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x05, 0x00,
}

var smbQuery = [...]byte{
	0x00, 0x00, 0x00, 0xa4, 0xff, 0x53, 0x4d, 0x42, 0x72, 0x00, 0x00, 0x00, 0x00, 0x08, 0x01, 0x40,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x06,
	0x00, 0x00, 0x01, 0x00, 0x00, 0x81, 0x00, 0x02, 0x50, 0x43, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f,
	0x52, 0x4b, 0x20, 0x50, 0x52, 0x4f, 0x47, 0x52, 0x41, 0x4d, 0x20, 0x31, 0x2e, 0x30, 0x00, 0x02,
	0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x4f, 0x46, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52,
	0x4b, 0x53, 0x20, 0x31, 0x2e, 0x30, 0x33, 0x00, 0x02, 0x4d, 0x49, 0x43, 0x52, 0x4f, 0x53, 0x4f,
	0x46, 0x54, 0x20, 0x4e, 0x45, 0x54, 0x57, 0x4f, 0x52, 0x4b, 0x53, 0x20, 0x33, 0x2e, 0x30, 0x00,
	0x02, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x31, 0x2e, 0x30, 0x00, 0x02, 0x4c, 0x4d, 0x31, 0x2e,
	0x32, 0x58, 0x30, 0x30, 0x32, 0x00, 0x02, 0x53, 0x61, 0x6d, 0x62, 0x61, 0x00, 0x02, 0x4e, 0x54,
	0x20, 0x4c, 0x41, 0x4e, 0x4d, 0x41, 0x4e, 0x20, 0x31, 0x2e, 0x30, 0x00, 0x02, 0x4e, 0x54, 0x20,
	0x4c, 0x4d, 0x20, 0x30, 0x2e, 0x31, 0x32, 0x00,
}

var (
	UNIQUE_NAMES = map[string]string{
		"\x00": "Workstation Service",
		"\x03": "Messenger Service",
		"\x06": "RAS Server Service",
		"\x1F": "NetDDE Service",
		"\x20": "Server Service",
		"\x21": "RAS Client Service",
		"\xBE": "Network Monitor Agent",
		"\xBF": "Network Monitor Application",
		"\x1D": "Master Browser",
		"\x1B": "Domain Master Browser",
	}

	GROUP_NAMES = map[string]string{
		"\x00": "Domain Name",
		"\x1C": "Domain Controllers",
		"\x1E": "Browser Service Elections",
	}

	NetBIOS_ITEM_TYPE = map[string]string{
		"\x01\x00": "NetBIOS computer name",
		"\x02\x00": "NetBIOS domain name",
		"\x03\x00": "DNS computer name",
		"\x04\x00": "DNS domain name",
		"\x05\x00": "DNS tree name",
		"\x07\x00": "Time stamp",
	}
)

type NbnsName struct {
	unique    string
	group     string
	msg       string
	osversion string
}

var netBiosCmd = &cobra.Command{
	Use:   "winscan",
	Short: "netbios、smb、oxid scan",
	PreRun: func(cmd *cobra.Command, args []string) {
		CreatFile(Output_result, Path_result)
		PrintScanBanner("netbios")
	},
	Run: func(cmd *cobra.Command, args []string) {
		start := time.Now()
		defer func() {
			Output_endtime(start)
		}()
		winscan()
	},
}

func winscan() {
	GetHost()
	if !netbios && !smb && !oxid {
		//r1 := netBiosScan()
		//r2 := smbScan()
		//r3 := oxidScan()
		//PrintResultNetbios(r1)
		//PrintResultSMB(r2)
		//PrintResultOxid(r3)
		nbt_smb_oxid()
		return
	}
	if netbios {
		r1 := netBiosScan()
		PrintResultNetbios(r1)
	}
	if smb {
		r2 := smbScan()
		PrintResultSMB(r2)
	}
	if oxid {
		r3 := oxidScan()
		PrintResultOxid(r3)
	}
}

func nbt_smb_oxid()  {
	ips, err := Parse_IP(Hosts)
	Checkerr(err)
	aliveserver := NewPortScan(ips, []int{139,445,135}, nbt_smb_oxi_scan,true)
	r := aliveserver.Run()
	PrintResultNetbios(r)
	PrintResultSMB(r)
	PrintResultOxid(r)
}

func nbt_smb_oxi_scan(ip string,port int) (string,int,error,[]string) {
	switch port {
	case 139:
		nbname, _ := netBIOS(ip)
		if nbname.msg != "" {
			return ip, port, nil, []string{nbname.msg}
		}
	case 445:
		conn, err := Getconn(fmt.Sprintf("%v:%v",ip,port))
		if err != nil {
			return ip, port, err, nil
		}
		defer conn.Close()
		ok,smbRes:=smbinfo(conn)
		if ok==nil{
			return ip,port,nil,smbRes
		}else {
			return ip,port,ok,nil
		}
	case 135:
		conn, err := Getconn(fmt.Sprintf("%v:%v",ip,port))
		if err != nil {
			return ip, port, err, nil
		}
		defer conn.Close()
		_,oxidres:=oxidIpInfo(conn)
		return ip,port,nil,oxidres
	default:
	}
	return ip, port, fmt.Errorf("err"), nil
}

func netBiosScan() map[string]*Openport {
	ips, err := Parse_IP(Hosts)
	Checkerr(err)
	aliveserver := NewPortScan(ips, []int{139}, netBiosIpInfo,true)
	r := aliveserver.Run()
	return r
}

func netBiosIpInfo(ip string, port int) (string, int, error, []string) {
	nbname, err := netBIOS(ip)
	if nbname.msg != "" {
		return ip, port, nil, []string{nbname.msg}
	}
	return ip, port, err, nil
}

func PrintResultNetbios(r map[string]*Openport) {
	Output("\n\r===========================netbios result list===========================\n", LightGreen)
	for i, data := range r {
		Output(fmt.Sprintf("Traget:%v\n", i), LightBlue)
		for _, s := range data.banner[139] {
			Output(fmt.Sprintf("\t%v\n", s), White)
		}
	}
}

func netBIOS(host string) (nbname NbnsName, err error) {
	nbname, err = getNbnsname(host)
	var payload0 []byte
	if err == nil {
		name := netbiosEncode(nbname.unique)
		payload0 = append(payload0, []byte("\x81\x00\x00D ")...)
		payload0 = append(payload0, name...)
		payload0 = append(payload0, []byte("\x00 EOENEBFACACACACACACACACACACACACA\x00")...)
	}
	realhost := fmt.Sprintf("%s:%v", host, 139)
	conn, err := Getconn(realhost)
	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()
	if err != nil {
		return
	}
	err = conn.SetDeadline(time.Now().Add(Timeout))
	if err != nil {
		return
	}

	if len(payload0) > 0 {
		_, err1 := conn.Write(payload0)
		if err1 != nil {
			return
		}
		_, err1 = readbytes(conn)
		if err1 != nil {
			return
		}
	}

	payload1 := []byte("\x00\x00\x00\x85\xff\x53\x4d\x42\x72\x00\x00\x00\x00\x18\x53\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x00\x00\x00\x62\x00\x02\x50\x43\x20\x4e\x45\x54\x57\x4f\x52\x4b\x20\x50\x52\x4f\x47\x52\x41\x4d\x20\x31\x2e\x30\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x31\x2e\x30\x00\x02\x57\x69\x6e\x64\x6f\x77\x73\x20\x66\x6f\x72\x20\x57\x6f\x72\x6b\x67\x72\x6f\x75\x70\x73\x20\x33\x2e\x31\x61\x00\x02\x4c\x4d\x31\x2e\x32\x58\x30\x30\x32\x00\x02\x4c\x41\x4e\x4d\x41\x4e\x32\x2e\x31\x00\x02\x4e\x54\x20\x4c\x4d\x20\x30\x2e\x31\x32\x00")
	payload2 := []byte("\x00\x00\x01\x0a\xff\x53\x4d\x42\x73\x00\x00\x00\x00\x18\x07\xc8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x00\x00\x40\x00\x0c\xff\x00\x0a\x01\x04\x41\x32\x00\x00\x00\x00\x00\x00\x00\x4a\x00\x00\x00\x00\x00\xd4\x00\x00\xa0\xcf\x00\x60\x48\x06\x06\x2b\x06\x01\x05\x05\x02\xa0\x3e\x30\x3c\xa0\x0e\x30\x0c\x06\x0a\x2b\x06\x01\x04\x01\x82\x37\x02\x02\x0a\xa2\x2a\x04\x28\x4e\x54\x4c\x4d\x53\x53\x50\x00\x01\x00\x00\x00\x07\x82\x08\xa2\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x02\xce\x0e\x00\x00\x00\x0f\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x33\x00\x37\x00\x39\x00\x30\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x69\x00\x63\x00\x65\x00\x20\x00\x50\x00\x61\x00\x63\x00\x6b\x00\x20\x00\x32\x00\x00\x00\x00\x00\x57\x00\x69\x00\x6e\x00\x64\x00\x6f\x00\x77\x00\x73\x00\x20\x00\x53\x00\x65\x00\x72\x00\x76\x00\x65\x00\x72\x00\x20\x00\x32\x00\x30\x00\x30\x00\x33\x00\x20\x00\x35\x00\x2e\x00\x32\x00\x00\x00\x00\x00")
	_, err = conn.Write(payload1)
	if err != nil {
		return
	}
	_, err = readbytes(conn)
	if err != nil {
		return
	}

	_, err = conn.Write(payload2)
	if err != nil {
		return
	}
	ret, err := readbytes(conn)
	if err != nil || len(ret) < 45 {
		return
	}

	num1, err := bytetoint(ret[43:44][0])
	if err != nil {
		return
	}
	num2, err := bytetoint(ret[44:45][0])
	if err != nil {
		return
	}
	length := num1 + num2*256
	if len(ret) < 48+length {
		return
	}
	os_version := ret[47+length:]
	tmp1 := bytes.ReplaceAll(os_version, []byte{0x00, 0x00}, []byte{124})
	tmp1 = bytes.ReplaceAll(tmp1, []byte{0x00}, []byte{})
	msg1 := string(tmp1[:len(tmp1)-1])
	nbname.osversion = msg1
	index1 := strings.Index(msg1, "|")
	if index1 > 0 {
		nbname.osversion = nbname.osversion[:index1]
	}
	nbname.msg += "\n\t-------------------------------------------\n\t"
	nbname.msg += msg1 + "\n\t"
	start := bytes.Index(ret, []byte("NTLMSSP"))
	if len(ret) < start+45 {
		return
	}
	num1, err = bytetoint(ret[start+40 : start+41][0])
	if err != nil {
		return
	}
	num2, err = bytetoint(ret[start+41 : start+42][0])
	if err != nil {
		return
	}
	length = num1 + num2*256
	num1, err = bytetoint(ret[start+44 : start+45][0])
	if err != nil {
		return
	}
	offset, err := bytetoint(ret[start+44 : start+45][0])
	if err != nil || len(ret) < start+offset+length {
		return
	}
	index := start + offset
	for index < start+offset+length {
		item_type := ret[index : index+2]
		num1, err = bytetoint(ret[index+2 : index+3][0])
		if err != nil {
			return
		}
		num2, err = bytetoint(ret[index+3 : index+4][0])
		if err != nil {
			return
		}
		item_length := num1 + num2*256
		item_content := bytes.ReplaceAll(ret[index+4:index+4+item_length], []byte{0x00}, []byte{})
		index += 4 + item_length
		if string(item_type) == "\x07\x00" {
			//Time stamp, 暂时不想处理
		} else if NetBIOS_ITEM_TYPE[string(item_type)] != "" {
			nbname.msg += fmt.Sprintf("%-22s: %s\n\t", NetBIOS_ITEM_TYPE[string(item_type)], string(item_content))
		} else if string(item_type) == "\x00\x00" {
			break
		} else {
			nbname.msg += fmt.Sprintf("Unknown: %s\n\t", string(item_content))
		}
	}
	nbname.msg=strings.TrimSpace(nbname.msg)
	return nbname, err
}
//
//func Proxyconn_udp() (proxy.Dialer,error) {
//	if Proxy==""{
//		return nil,fmt.Errorf("")
//	}else {
//		if strings.ContainsAny(Proxy,"@")&&strings.Count(Proxy,"@")==1{
//			info:=strings.Split(Proxy,"@")
//			userpass:=strings.Split(info[0],":")
//			auth:= proxy.Auth {userpass[0],userpass[1]}
//			dialer,err:=proxy.SOCKS5("udp",info[1],&auth,proxy.Direct)
//			return dialer,err
//		}else {
//			if strings.ContainsAny(Proxy,":")&&strings.Count(Proxy,":")==1{
//				dialer,err:=proxy.SOCKS5("udp",Proxy,nil,proxy.Direct)
//				return dialer,err
//			}
//		}
//	}
//	return nil,fmt.Errorf("proxy error")
//}

func getNbnsname(host string) (nbname NbnsName, err error) {
	senddata1 := []byte{102, 102, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 32, 67, 75, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 0, 0, 33, 0, 1}
	//d,err:=Proxyconn_udp()
	//Checkerr(err)
	realhost := fmt.Sprintf("%s:%v", host, 137)
	conn, err := net.DialTimeout("udp", realhost, Timeout)
	defer func() {
		if conn != nil {
			conn.Close()
		}
	}()
	if err != nil {
		return
	}
	err = conn.SetDeadline(time.Now().Add(Timeout))
	if err != nil {
		return
	}
	_, err = conn.Write(senddata1)
	if err != nil {
		return
	}
	text, err := readbytes(conn)
	if err != nil {
		return
	}
	if len(text) < 57 {
		return nbname, fmt.Errorf("no names available")
	}
	num, err := bytetoint(text[56:57][0])
	if err != nil {
		return
	}
	data := text[57:]
	msg:=""
	for i := 0; i < num; i++ {
		if len(data) < 18*i+16 {
			break
		}
		name := string(data[18*i : 18*i+15])
		flag_bit := data[18*i+15 : 18*i+16]
		if GROUP_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
			msg += fmt.Sprintf("%s G %s\n\t", name, GROUP_NAMES[string(flag_bit)])
		} else if UNIQUE_NAMES[string(flag_bit)] != "" && string(flag_bit) != "\x00" {
			msg += fmt.Sprintf("%s U %s\n\t", name, UNIQUE_NAMES[string(flag_bit)])
		} else if string(flag_bit) == "\x00" || len(data) >= 18*i+18 {
			name_flags := data[18*i+16 : 18*i+18][0]
			if name_flags >= 128 {
				nbname.group = strings.Replace(name, " ", "", -1)
				msg += fmt.Sprintf("%s G %s\n\t", name, GROUP_NAMES[string(flag_bit)])
			} else {
				nbname.unique = strings.Replace(name, " ", "", -1)
				msg += fmt.Sprintf("%s U %s\n\t", name, UNIQUE_NAMES[string(flag_bit)])
			}
		} else {
			msg += fmt.Sprintf("%s \n\t", name)
		}
	}
	nbname.msg += msg
	nbname.msg=strings.TrimSpace(nbname.msg)
	return
}

func readbytes(conn net.Conn) (result []byte, err error) {
	buf := make([]byte, 4096)
	for {
		count, err := conn.Read(buf)
		if err != nil {
			break
		}
		result = append(result, buf[0:count]...)
		if count < 4096 {
			break
		}
	}
	return result, err
}

func bytetoint(text byte) (int, error) {
	num1 := fmt.Sprintf("%v", text)
	num, err := strconv.Atoi(num1)
	return num, err
}

func netbiosEncode(name string) (output []byte) {
	var names []int
	src := fmt.Sprintf("%-16s", name)
	for _, a := range src {
		char_ord := int(a)
		high_4_bits := char_ord >> 4
		low_4_bits := char_ord & 0x0f
		names = append(names, high_4_bits, low_4_bits)
	}
	for _, one := range names {
		out := one + 0x41
		output = append(output, byte(out))
	}
	return
}

func smbScan() map[string]*Openport {
	ips, err := Parse_IP(Hosts)
	Checkerr(err)
	aliveserver1 := NewPortScan(ips, []int{445}, Connectsmb,true)
	r1 := aliveserver1.Run()
	//PrintResultSMB(r1)
	return r1
}

func smbinfo(conn net.Conn) (error,[]string) {
	buf := make([]byte, 1024)
	_, err := conn.Write(smbQuery[:])
	if err != nil {
		return err,nil
	}
	_, err = conn.Read(buf)
	if err != nil {
		return err,nil
	}
	if len(buf)<81{
		return fmt.Errorf(""),nil
	}
	buf = buf[81:]
	end := bytes.Index(buf, []byte{0x00, 0x00, 0x00})
	var smbRes []string
	domain := buf[:end]
	hostname := buf[end:]
	domain = bytes.Replace(domain, []byte{0x00}, []byte(""), -1)
	hostname = bytes.Replace(hostname, []byte{0x00}, []byte(""), -1)
	smbRes = append(smbRes, "domain: "+string(domain))
	smbRes = append(smbRes, "hostname: "+string(hostname))
	return nil,smbRes
}

func Connectsmb(ip string, port int) (string, int, error, []string) {
	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%v:%v", ip, port), Timeout)
	if err != nil {
		return ip, port, err, nil
	}
	defer conn.Close()
	ok,smbRes:=smbinfo(conn)
	if ok==nil{
		return ip,port,nil,smbRes
	}else {
		return ip,port,ok,nil
	}
}

func PrintResultSMB(r map[string]*Openport) {
	Output("\n\r============================smb result list=============================\n", LightGreen)
	for i, data := range r {
		Output(fmt.Sprintf("Traget:%v\n", i), LightBlue)
		for _, s := range data.banner[445] {
			Output(fmt.Sprintf("\t%v\n", s), White)
		}
	}
}

func oxidScan() map[string]*Openport {
	ips, err := Parse_IP(Hosts)
	Checkerr(err)
	aliveserver := NewPortScan(ips, []int{135}, Connectoxid,true)
	r := aliveserver.Run()
	return r
	//PrintResultOxid(r)
}

func Connectoxid(ip string,port int) (string, int, error, []string) {
	conn, err := net.DialTimeout("tcp", fmt.Sprintf("%v:%v", ip, port), Timeout)
	if err != nil {
		return ip, port, err, nil
	}
	defer conn.Close()
	err,oxidres:=oxidIpInfo(conn)
	if err!=nil{
		return ip,port,err,nil
	}else {
		return ip,port,nil,oxidres
	}
}

func oxidIpInfo(conn net.Conn) (error, []string) {

	buf := make([]byte, 256)
	_, err := conn.Write(oxidQuery1[:])
	if err != nil {
		return err, nil
	}
	_, err = conn.Read(buf)
	if err != nil {
		return  err, nil
	}
	_, err = conn.Write(oxidQuery2[:])
	if err != nil {
		return  err, nil
	}
	_, err = conn.Read(buf)
	if err != nil {
		return  err, nil
	}
	end := bytes.Index(buf, []byte{0x00, 0x00, 0x09, 0x00, 0xff, 0xff, 0x00, 0x00})
	if len(buf)<40||end==-1{
		return fmt.Errorf(""),nil
	}
	buf = buf[40:end]
	var oxidRes []string
	for i := bytes.Index(buf, []byte{0x00, 0x00, 0x00}); i != -1; {
		res := buf[1:i]
		res = bytes.Replace(res, []byte{0x00}, []byte(""), -1)
		oxidRes = append(oxidRes, string(res))
		buf = buf[i+3:]
		i = bytes.Index(buf, []byte{0x00, 0x00, 0x00})
	}
	return  nil, oxidRes
}


func PrintResultOxid(r map[string]*Openport) {
	Output("\n\r===========================oxid result list=============================\n", LightGreen)
	for i, data := range r {
		Output(fmt.Sprintf("Traget:%v\n", i), LightBlue)
		for _, s := range data.banner[135] {
			Output(fmt.Sprintf("\t%v\n", s), White)
		}
	}
}

func init() {
	rootCmd.AddCommand(netBiosCmd)
	netBiosCmd.Flags().StringVar(&Hostfile,"hostfile","","Set host file")
	netBiosCmd.Flags().StringVarP(&Hosts, "host", "H", "", "Set target")
	netBiosCmd.Flags().BoolVar(&netbios, "netbios", false, "netbios scan")
	netBiosCmd.Flags().BoolVar(&smb, "smb", false, "smb scan")
	netBiosCmd.Flags().BoolVar(&oxid, "oxid", false, "oxid scan")
}
